/*
 * GPUC
 * 
 * Copyright © 2008 Attila T. Áfra <attila.afra@gmail.com>
 * 
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along with this program. If
 * not, see <http://www.gnu.org/licenses/>.
 */

#include <stdlib.h>
#include "BufferPrivate.h"

/*------------------------------------------------------------------------------------------------*/

GPUC_API GPUC_Buffer
GPUC_Buffer_create(size_t           size,
                   GPUC_BufferUsage usage)
{
    GPUC_Buffer buffer;

    if (!GPUC_initialized)
        return NULL;

    if ((usage != GPUC_BufferUsage_Upload) && (usage != GPUC_BufferUsage_Download))
    {
        GPUC_error = GPUC_Error_InvalidArgument;
        return NULL;
    }

    // Allocate memory for the buffer object (1)
    buffer = (GPUC_Buffer)malloc(sizeof (struct GPUC_Buffer_Object));

    if (buffer == NULL)
    {
        GPUC_error = GPUC_Error_OutOfMemory;
        return NULL;
    }

    // Set buffer parameters
    if (usage == GPUC_BufferUsage_Upload)
    {
        buffer->pboTarget = GL_PIXEL_UNPACK_BUFFER;
        buffer->pboUsage  = GL_STREAM_DRAW;
    }
    else
    {
        buffer->pboTarget = GL_PIXEL_PACK_BUFFER;
        buffer->pboUsage  = GL_STREAM_READ;
    }

    // Create pixel buffer object (2)
    glGenBuffers(1, &buffer->pboId);
    glBindBuffer(buffer->pboTarget, buffer->pboId);

    // Allocate memory
    GPUC_ignoreGlErrors();
    glBufferData(buffer->pboTarget, (GLsizeiptr)size, 0, buffer->pboUsage);

    switch (GPUC_checkGlError(GL_OUT_OF_MEMORY))
    {
    case 0:
        GPUC_error = GPUC_Error_OutOfMemory;
        goto cleanup2;

    case 1:
        GPUC_error = GPUC_Error_Unknown;
        goto cleanup2;
    }

    // Success
    glBindBuffer(buffer->pboTarget, 0);

    buffer->size   = size;
    buffer->usage  = usage;
    buffer->mapped = 0;
    return buffer;

    // Error
cleanup2:
    glDeleteBuffers(1, &buffer->pboId);

/*cleanup1:*/
    free(buffer);

    return NULL;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_Buffer_destroy(GPUC_Buffer buffer)
{
    if (buffer->mapped)
    {
        GPUC_error = GPUC_Error_InvalidOperation;
        return;
    }

    glDeleteBuffers(1, &buffer->pboId);
    free(buffer);
    --GPUC_objectCount;
    GPUC_error = GPUC_Error_None;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void *
GPUC_Buffer_map(GPUC_Buffer buffer,
                GPUC_Access access)
{
    void   *data;
    GLenum  pboAccess;

    switch (access)
    {
    case GPUC_Access_Read:
        pboAccess = GL_READ_ONLY;
        break;

    case GPUC_Access_Write:
        pboAccess = GL_WRITE_ONLY;
        break;

    case GPUC_Access_ReadWrite:
        pboAccess = GL_READ_WRITE;
        break;

    default:
        GPUC_error = GPUC_Error_InvalidArgument;
        return NULL;
    }

    if (buffer->mapped)
    {
        GPUC_error = GPUC_Error_InvalidOperation;
        return NULL;
    }

    glBindBuffer(buffer->pboTarget, buffer->pboId);

    GPUC_ignoreGlErrors();
    data = glMapBuffer(buffer->pboTarget, pboAccess);

    switch (GPUC_checkGlError(GL_OUT_OF_MEMORY))
    {
    case 0:
        GPUC_error = GPUC_Error_OutOfMemory;
        break;

    case 1:
        GPUC_error = GPUC_Error_Unknown;
        break;

    default:
        buffer->mapped = 1;
        GPUC_error = GPUC_Error_None;
    }

    glBindBuffer(buffer->pboTarget, 0);
    return data;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API void
GPUC_Buffer_unmap(GPUC_Buffer buffer)
{
    if (!buffer->mapped)
    {
        GPUC_error = GPUC_Error_InvalidOperation;
        return;
    }

    glBindBuffer(buffer->pboTarget, buffer->pboId);
    glUnmapBuffer(buffer->pboTarget);
    glBindBuffer(buffer->pboTarget, 0);

    buffer->mapped = 0;
    GPUC_error = GPUC_Error_None;
}

/*------------------------------------------------------------------------------------------------*/

GPUC_API size_t
GPUC_Buffer_getSize(GPUC_Buffer buffer)
{
    GPUC_error = GPUC_Error_None;
    return buffer->size;
}

/*------------------------------------------------------------------------------------------------*/
